package jwtauth

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/hxchjm/go-admin/pkg/db"
	"gitee.com/hxchjm/go-admin/pkg/ecode"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt"
	"github.com/hxchjm/log"
	"net/http"
	"time"
)

const (
	LogoutTokenKey     = "logout_token_key:%s"
	ContextJwtClaim    = "claim"
	ContextJwtUid      = "uid"
	ContextJwtTokenKey = "x-token"
)

var PermissionWhiteList = map[string]struct{}{
	"/api/v1/login":        {},
	"/api/v1/captcha":      {},
	"/filestore/download":  {},
	"/debug/pprof/profile": {},
}

type CustomClaims struct {
	UID       int
	NickName  string
	RoleName  string
	RoleKey   string
	DataScope string
	DeptId    int
	RoleId    int
	jwt.StandardClaims
}

func Auth() gin.HandlerFunc {
	return func(c *gin.Context) {
		defer c.Abort()
		//我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中,
		//不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
		if _, ok := PermissionWhiteList[c.Request.URL.Path]; ok {
			c.JSON(http.StatusOK, ecode.New(-1, "请登录"))
			return
		}
		token := c.Request.Header.Get("Authorization")
		if token == "" {
			c.JSON(http.StatusOK, ecode.New(-1, "请登录"))
			//c.Abort()
			return
		}
		// parseToken 解析token包含的信息
		claims, err := NewJWT().ParseToken(token)
		if err != nil {
			c.JSON(http.StatusOK, ecode.ERRTokenExpired)
			//c.Abort()
			return
		}
		if err = claims.Valid(); err != nil {
			c.JSON(http.StatusOK, ecode.Wrap(ecode.ERRTokenExpired, err.Error()))
			//c.Abort()
			return
		}
		/*if err != nil {
			if err == TokenExpired {
				if err == TokenExpired {
					c.JSON(http.StatusUnauthorized, ecode.New(-1, "授权已过期，请重新登录！"))
					c.Abort()
					return
				}
			}
			c.JSON(http.StatusUnauthorized, ecode.New(-1, "未登录"))
			log.Errorf("user hasn't login ")
			c.Abort()
			return
		}*/
		ctx := context.Background()
		res, err := db.GetRedis().Get(ctx, fmt.Sprintf(LogoutTokenKey, token)).Result()
		if err != nil {
			if err.Error() != "redis: nil" {
				log.Errorf(ctx, "redis get err (%+v),uid(%+v)", err, claims.UID)
			}
		}
		if res == "1" {
			c.JSON(http.StatusOK, ecode.ERRTokenExpired)
			log.Errorf(ctx, "user has logout")
			//c.Abort()
			return
		}
		c.Set(ContextJwtClaim, claims)
		//c.Set(ContextJwtUid, claims.UID)
		//c.Set(ContextJwtTokenKey, token)
		c.Next()
	}
}

type JWT struct {
	SigningKey []byte
}

var (
	TokenExpired     = errors.New("Token is expired ")
	TokenNotValidYet = errors.New("Token not active yet ")
	TokenMalformed   = errors.New("That's not even a token ")
	TokenInvalid     = errors.New("Couldn't handle this token: ")
)

func NewJWT() *JWT {
	return &JWT{
		[]byte("a5e8b8943644ccd67aab88"), //可以设置过期时间
	}
}

// GenerateToken 创建一个token
func (j *JWT) GenerateToken(claims CustomClaims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(j.SigningKey)
}

// ParseToken 解析 token
func (j *JWT) ParseToken(tokenString string) (*CustomClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (i interface{}, e error) {
		return j.SigningKey, nil
	})
	if err != nil {
		if ve, ok := err.(*jwt.ValidationError); ok {
			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
				return nil, TokenMalformed
			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
				// Token is expired
				return nil, TokenExpired
			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
				return nil, TokenNotValidYet
			} else {
				return nil, TokenInvalid
			}
		}
	}
	if token != nil {
		if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
			return claims, nil
		}
		return nil, TokenInvalid
	} else {
		return nil, TokenInvalid
	}
}

// RefreshToken 更新token
func (j *JWT) RefreshToken(tokenString string) (string, error) {
	jwt.TimeFunc = func() time.Time {
		return time.Unix(0, 0)
	}
	token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return j.SigningKey, nil
	})
	if err != nil {
		return "", err
	}
	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		jwt.TimeFunc = time.Now
		claims.StandardClaims.ExpiresAt = time.Now().Add(1 * time.Hour).Unix()
		return j.GenerateToken(*claims)
	}
	return "", TokenInvalid
}

func GetClaims(ctx context.Context) *CustomClaims {
	if c, ok := ctx.(*gin.Context); ok {
		claims, exists := c.Get(ContextJwtClaim)
		if !exists {
			return &CustomClaims{}
		}
		return claims.(*CustomClaims)
	}
	return &CustomClaims{}
}
